/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once

#include "Vector.h"

using System::Runtime::InteropServices::OutAttribute;

namespace OptixDotNet {
namespace Math
{
	public value class Matrix4x4
	{
	public:
		Matrix4x4( Vector4 x, Vector4 y, Vector4 z, Vector4 w ) : X(x), Y(y), Z(z), W(w) { }

		static property Matrix4x4 Identity { Matrix4x4 get() { return Matrix4x4( Vector4::UnitX, Vector4::UnitY, Vector4::UnitZ, Vector4::UnitW ); } }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(Matrix4x4::typeid); } }

		static Matrix4x4 operator * ( Matrix4x4 l, Matrix4x4 r )
		{
			Matrix4x4 result;
			Matrix4x4 transpose;
			Matrix4x4::Transpose( r, transpose );

			result.X.Set( l.X.Dot( transpose.X ), l.X.Dot( transpose.Y ), l.X.Dot( transpose.Z ), l.X.Dot( transpose.W ) );
			result.Y.Set( l.Y.Dot( transpose.X ), l.Y.Dot( transpose.Y ), l.Y.Dot( transpose.Z ), l.Y.Dot( transpose.W ) );
			result.Z.Set( l.Z.Dot( transpose.X ), l.Z.Dot( transpose.Y ), l.Z.Dot( transpose.Z ), l.Z.Dot( transpose.W ) );
			result.W.Set( l.W.Dot( transpose.X ), l.W.Dot( transpose.Y ), l.W.Dot( transpose.Z ), l.W.Dot( transpose.W ) );
			return result;
		}

		static Matrix4x4 operator * ( Matrix4x4 l, float x )
		{
			Matrix4x4 result;

			result.X.Set( l.X * x );
			result.Y.Set( l.Y * x );
			result.Z.Set( l.Z * x );
			result.W.Set( l.W * x );
			return result;
		}

		static void Multiply( Matrix4x4% left, Matrix4x4% right, [Out] Matrix4x4% result );
		static Matrix4x4 Multiply( Matrix4x4% left, Matrix4x4% right );
		void Multiply( Matrix4x4 right );

		float Determinant();

		static void Inverse( Matrix4x4% matrix, [Out] Matrix4x4% inverse );
		static Matrix4x4 Inverse( Matrix4x4% matrix );
		void Invert();

		static void Transpose( Matrix4x4% matrix, [Out] Matrix4x4% transpose );	
		static Matrix4x4 Transpose( Matrix4x4% matrix );
		void Transpose();
		
		static Matrix4x4 CreateScale( float scale);
		static Matrix4x4 CreateScale( Vector3 scale );

		static Matrix4x4 CreateTranslation( float translation );
		static Matrix4x4 CreateTranslation( Vector3 translation );

		static Matrix4x4 CreateRotationAxis( Vector3 axis, float angle );
		static Matrix4x4 CreateRotationX( float angle );
		static Matrix4x4 CreateRotationY( float angle );
		static Matrix4x4 CreateRotationZ( float angle );

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z + ", " + W );
		}

	public:
		Vector4 X, Y, Z, W;
	};
}
}